﻿/**
 * @file Account.c
 * @author monster
 * @date 2024/4/4
 */

#include "Account.h"
#include "DataCenter.h"
#include "DataCenterLog.h"

#include <string.h>

Account::Account(const char *id, DataCenter *center,
                 uint32_t size, void *user_data) :
        ID(id),
        Center(center),
        Buffer(size),
        UserData(user_data) {

    eventCallback = nullptr;


    Center->AddAccount(this);
    DC_LOG("Account: \"%s\" created", ID);
}

Account::~Account() {
    DC_LOG_INFO("Account[%s] deleting...", ID);

    /* Subscribers unfollow */
    for (auto iter: subscribers) {
        iter->Unsubscribe(ID);
        DC_LOG_INFO("sub[%s] unsubscribed pub[%s]", iter->ID, ID);
    }

    /* Ask publisher to delete this subscriber */
    for(auto iter : publishers){
        Center->Remove(&iter->subscribers, this);
    }

    /* DataCenter delete the account */
    Center->RemoveAccount(this);
    DC_LOG_INFO("Account[%s] deleted", ID);
}

/**
  * @brief  Set event callback
  * @param  callback: Callback function pointer
  * @retval None
  */
void Account::SetEventCallback(Account::EventCallback_t callback) {
    eventCallback = callback;
}


/**
 * @brief Subscribe to Publisher
 * @param pubID Publisher ID
 * @return Pointer to Publisher
*/
Account *Account::Subscribe(const char *pubID) {
    /*Not allowed to subscribe itself*/
    if (strcmp(pubID, this->ID) == 0) {
        DC_LOG_ERROR("Account: '%s' try to subscribe itself", pubID);
        return nullptr;
    }

    /*Multi subscribe*/
    Account *pub = Center->Find(&publishers, pubID);
    if (pub != nullptr) {
        DC_LOG_WARN("Account: '%s' try to subscribe itself", pubID);
        return nullptr;
    }

    /*Publisher account exist check*/
    pub = Center->SearchAccount(pubID);
    if (pub == nullptr) {
        DC_LOG_WARN("Publisher: '%s' was not exist", pubID);
        return nullptr;
    }

    publishers.push_back(pub);

    pub->subscribers.push_back(this);

    DC_LOG("\"%s\" subscribed \"%s\"", ID, pubID);

    return pub;
}

/**
 * @brief Unsubscribe from publisher
 * @param pubID Publisher ID
 * @return If unsubscribe is successful return true;
*/
bool Account::Unsubscribe(const char *pubID) {
    Account *pub = Center->Find(&publishers, pubID);
    if (pub == nullptr) {
        DC_LOG_WARN("'%s' was not subscribe '%s'", ID, pubID);
        return false;
    }

    Center->Remove(&publishers, pub);
    Center->Remove(&pub->subscribers, this);

    return true;
}

/**
  * @brief  Submit data to cache
  * @param  data: Pointer to data
  * @param  size: The size of the data
  * @retval Return true if the submission is successful
  */
bool Account::Commit(void *data, uint32_t size) {

    if (!Buffer.GetCache()->size) {
        DC_LOG_ERROR("pub[%s] has not cache", ID);
        return false;
    }

    if (!data || size != Buffer.GetCache()->size) {
        DC_LOG_ERROR("pub[%s] commit data type error", ID);
        return false;
    }

    Buffer.WriteData(data, size);

    DC_LOG_INFO("pub[%s] commit data(0x%p)[%d] done",
                ID, data, size);

    return true;
}


/**
  * @brief  Publish data to all subscribers
  * @param  None
  * @retval error code
  */
int Account::Publisher() {
    int ret = RES_UNKNOWN;

    if (Buffer.GetCache()->size == 0) {
        DC_LOG_ERROR("pub[%s] has not cache", ID);
        return RES_NO_BUFFER;
    }

    void *rBuf = nullptr;
    if (!Buffer.GetReadBuff(&rBuf)) {
        DC_LOG_WARN("pub[%s] data was not commit", ID);
        return RES_NO_COMMIT;
    }

    EventParams_t param;
    param.event = EVENT_PUBLISH;
    param.tran  = this;
    param.recv  = nullptr;
    param.data  = rBuf;
    param.size  = Buffer.GetCache()->size;

    for (auto iter: subscribers) {
        Account         *sub     = iter;
        EventCallback_t callback = sub->eventCallback;

        DC_LOG_INFO("pub[%s] publish data(0x%p)[%d] >> sub[%s]...",
                    ID, param.data, param.size, sub->ID);

        if (!callback) {
            DC_LOG_INFO("sub[%s] not register callback", sub->ID);
            continue;
        }

        param.recv = sub;
        ret = callback(sub, &param);
        DC_LOG_INFO("publish done: %d", ret);
    }

#if ACCOUNT_DISCARD_READ_DATA
    Buffer.ReadDone();
#endif

    return ret;
}

/**
 * Pull data from the publisher
 * @param pubID Publisher ID
 * @param data  Pointer to data cache
 * @param size  size of the data cache
 * @return error code
 */
int Account::Pull(const char *pubID, void *data, uint32_t size) {
    /* Check the pubID is subscribed */
    Account *pub = Center->Find(&publishers, pubID);
    if (pub == nullptr) {
        DC_LOG_ERROR("sub[%s] was not subscribed pub[%s]", ID, pubID);
        return RES_NOT_FOUND;
    }
    return Pull(pub, data, size);
}

int Account::Pull(Account *pub, void *data, uint32_t size) {
    if (pub == nullptr) {
        return RES_NOT_FOUND;
    }

    DC_LOG_INFO("sub[%s] pull << data(0x%p)[%d] << pub[%s]...",
                ID, data, size, pub->ID);

    EventCallback_t callback = pub->eventCallback;

    /* Used account callback */
    if (callback != nullptr) {
        EventParams_t param;
        param.event = EVENT_PULL;
        param.tran  = this;
        param.recv  = pub;
        param.data  = data;
        param.size  = size;

        int ret = callback(pub, &param);

        DC_LOG_INFO("pull done: %d", ret);
        return ret;
    }

    /* Not callback pull cache buffer data */
    DC_LOG_INFO("pub[%s] not register pull callback, read commit cache...", pub->ID);
    if (pub->Buffer.Size() != size) {
        DC_LOG_ERROR("data size pub[%s]:%d != sub[%s]:%d",
                     pub->ID, pub->Buffer.Size(),
                     this->ID, size);

        return RES_SIZE_MISMATCH;
    }

    if (!pub->Buffer.ReadData(data, size)) {
        DC_LOG_WARN("pub[%s] data was not commit!", pub->ID);
    }

    DC_LOG_INFO("read data done");
    return RES_OK;
}

int Account::Notify(const char *pubID, void *data, uint32_t size) {
    Account *pub = Center->Find(&publishers, pubID);
    if (pub == nullptr) {
        DC_LOG_ERROR("sub[%s] was not subscribed pub[%s]", ID, pubID);
        return RES_NOT_FOUND;
    }
    return Notify(pub, data, size);
}

int Account::Notify(Account *pub, void *data, uint32_t size) {
    if (pub == nullptr) {
        return RES_NOT_FOUND;
    }

    DC_LOG_INFO("sub[%s] notify >> data(0x%p)[%d] >> pub[%s] ...",
                ID, data, size, pub->ID);

    EventCallback_t callback = pub->eventCallback;
    if (callback != nullptr) {
        EventParams_t param;
        param.event = EVENT_NOTIFY;
        param.tran  = this;
        param.recv  = pub;
        param.data  = data;
        param.size  = size;

        int ret = callback(pub, &param);

        DC_LOG_INFO("Notify done: %d", ret);
        return ret;
    }

    DC_LOG_WARN("pub[%s] not register callback", pub->ID);
    return RES_NO_CALLBACK;
}


